Die Boost C++ Bibliotheken


Kapitel 16: Cast-Operatoren


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


16.1 Allgemeines

Der C++ Standard definiert die vier Cast-Operatoren static_cast, dynamic_cast, const_cast und reinterpret_cast. Die beiden Bibliotheken Boost.Conversion und Boost.NumericConversion definieren nun zusätzliche Cast-Operatoren, die für bestimmte Typumwandlungen spezialisiert sind.


16.2 Boost.Conversion

Die Bibliothek Boost.Conversion besteht nur aus zwei Headerdateien. Während in boost/cast.hpp die beiden Cast-Operatoren boost::polymorphic_cast und boost::polymorphic_downcast definiert sind, steht über boost/lexical_cast.hpp ein Cast-Operator boost::lexical_cast zur Verfügung.

Sinn und Zweck der Cast-Operatoren boost::polymorphic_cast und boost::polymorphic_downcast ist es, eine dynamische Typumwandlung, die normalerweise mit dynamic_cast erfolgt, zu präzisieren. Sehen Sie sich dazu folgendes Beispiel an.

struct father 
{ 
  virtual ~father() { }; 
}; 

struct mother 
{ 
  virtual ~mother() { }; 
}; 

struct child : 
  public father, 
  public mother 
{ 
}; 

void func(father *f) 
{ 
  child *c = dynamic_cast<child*>(f); 
} 

int main() 
{ 
  child *c = new child; 
  func(c); 

  father *f = new child; 
  mother *m = dynamic_cast<mother*>(f); 
} 

Im obigen Programm wird der Cast-Operator dynamic_cast zweimal eingesetzt: In der Funktion func() wandelt er den Zeiger auf eine Elternklasse in den Zeiger auf eine Kindklasse um. In der Funktion main() wandelt er den Zeiger auf eine Elternklasse in den Zeiger auf eine andere Elternklasse um. Die erste Umwandlung wird als Downcast bezeichnet, die zweite Umwandlung als Crosscast.

Indem die Cast-Operatoren von Boost.Conversion eingesetzt werden, kann ein Downcast von einem Crosscast im Code unterschieden werden.

#include <boost/cast.hpp> 

struct father 
{ 
  virtual ~father() { }; 
}; 

struct mother 
{ 
  virtual ~mother() { }; 
}; 

struct child : 
  public father, 
  public mother 
{ 
}; 

void func(father *f) 
{ 
  child *c = boost::polymorphic_downcast<child*>(f); 
} 

int main() 
{ 
  child *c = new child; 
  func(c); 

  father *f = new child; 
  mother *m = boost::polymorphic_cast<mother*>(f); 
} 

Der Cast-Operator boost::polymorphic_downcast kann nur für Downcasts verwendet werden. Er verwendet intern static_cast, um die Umwandlung durchzuführen. Da static_cast nicht dynamisch überprüft, ob die Typumwandlung gültig ist, darf boost::polymorphic_downcast nur angewandt werden, wenn sicher ist, dass die Typumwandlung durchgeführt werden darf. Zur Kontrolle greift boost::polymorphic_downcast in Debug-Versionen auf dynamic_cast zu und überprüft mit assert(), ob die Typumwandlung erfolgen darf. Diese Überprüfung findet aber wie gesagt nur statt, wenn das Makro NDEBUG nicht definiert ist, was üblicherweise nur in Debug-Versionen der Fall ist.

Während mit boost::polymorphic_downcast ein Downcast möglich ist, muss boost::polymorphic_cast für einen Crosscast verwendet werden. Dieser Cast-Operator verwendet intern dynamic_cast, da dies der einzige Cast-Operator ist, der tatsächlich einen Crosscast durchführen kann. Der Grund, warum es dennoch Sinn macht, boost::polymorphic_cast einzusetzen, ist, dass dieser Cast-Operator im Fehlerfall eine Ausnahme vom Typ std::bad_cast wirft. dynamic_cast hingegen gibt 0 zurück, wenn die Typumwandlung fehlschlägt. Anstatt den Rückgabewert selbst auf 0 zu überprüfen, wird die Überprüfung von boost::polymorphic_cast abgenommen.

Beide Cast-Operatoren boost::polymorphic_downcast und boost::polymorphic_cast können nur verwendet werden, wenn Zeiger umgewandelt werden müssen. Ansonsten muss auf dynamic_cast zugegriffen werden. So kann boost::polymorphic_downcast, das auf static_cast basiert, Objekte vom Typ einer Elternklasse nicht in Objekte vom Typ einer Kindklasse umwandeln. Es macht außerdem keinen Sinn, boost::polymorphic_cast einzusetzen, wenn andere Typen als Zeiger umgewandelt werden sollen, weil dynamic_cast dann sowieso eine Ausnahme vom Typ std::bad_cast wirft.

Während der Einsatz von boost::polymorphic_downcast und boost::polymorphic_cast nicht wirklich zwingend ist, weil die entsprechenden Typumwandlungen auch so wie bisher mit dynamic_cast durchgeführt werden können, bietet Boost.Conversion einen zweiten Cast-Operator an, der in der Praxis oft nützlich ist. Sehen Sie sich dazu folgendes Beispiel an.

#include <boost/lexical_cast.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  std::string s = boost::lexical_cast<std::string>(169); 
  std::cout << s << std::endl; 
  double d = boost::lexical_cast<double>(s); 
  std::cout << d << std::endl; 
} 

Der Cast-Operator boost::lexical_cast kann Zahlen verschiedener Datentypen umwandeln. So wird im obigen Programm die Ganzzahl 169 zuerst in einen String umgewandelt, um dann in einem zweiten Schritt den String in eine Gleitkommazahl umzuwandeln.

boost::lexical_cast verwendet intern Streams, um Umwandlungen durchzuführen. Deswegen können grundsätzlich nur Typen umgewandelt werden, für die die Operatoren operator<<() und operator>>() überladen sind. Der Vorteil von boost::lexical_cast ist also der, dass eine Typumwandlung in einer einzigen Zeile erfolgt und Sie nicht selber Streams anlegen müssen. Da in diesem Fall unter Umständen auch nicht sofort ersichtlich ist, dass auf Streams lediglich zur Typumwandlung zugegriffen wird, ist ein Cast-Operator wie boost::lexical_cast wesentlich aussagekräftiger.

Beachten Sie, dass boost::lexical_cast nicht unbedingt immer auf Streams zugreift. So kann dieser Cast-Operator für verschiedene Datentypen optimiert sein.

Schlägt eine Umwandlung fehl, wird eine Ausnahme vom Typ boost::bad_lexical_cast geworfen. Diese Klasse ist von std::bad_cast abgeleitet.

#include <boost/lexical_cast.hpp> 
#include <string> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    int i = boost::lexical_cast<int>("abc"); 
    std::cout << i << std::endl; 
  } 
  catch (boost::bad_lexical_cast &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

Obiges Programm wirft eine Ausnahme, weil der String "abc" nicht in eine Zahl vom Typ int umgewandelt werden kann.


16.3 Boost.NumericConversion

Die Bibliothek Boost.NumericConversion kann dann eingesetzt werden, wenn Zahlen eines Typs in Zahlen eines anderen Typs umgewandelt werden sollen. Derartiges kann in C++ auch implizit geschehen, wie im folgenden Programm zu sehen.

#include <iostream> 

int main() 
{ 
  int i = 0x10000; 
  short s = i; 
  std::cout << s << std::endl; 
} 

Das Beispielprogramm wird ohne Fehler kompiliert, weil die Umwandlung von int zu short in C++ automatisch erfolgt. Obwohl es keinen Compilerfehler gibt und das Programm ausgeführt werden kann, kann das Ergebnis der Umwandlung nicht vorhergesagt werden, sondern hängt vom Compiler und dessen Implementation ab. Das Problem ist, dass die Zahl 0x10000 in der Variablen i zu groß ist, um in einer short-Variablen gespeichert werden zu können. Für diesen Fall ist laut dem C++ Standard das Ergebnis "implementation defined". So gibt obiges Programm mit Visual C++ 2008 kompiliert 0 aus. Der Zahlenwert in s unterscheidet sich demnach deutlich von dem in i.

Um sicherzustellen, dass es bei einer Umwandlung von Zahlen zu keinen derartigen Fehlern kommt, kann der Cast-Operator boost::numeric_cast eingesetzt werden.

#include <boost/numeric/conversion/cast.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    int i = 0x10000; 
    short s = boost::numeric_cast<short>(i); 
    std::cout << s << std::endl; 
  } 
  catch (boost::numeric::bad_numeric_cast &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

boost::numeric_cast wird genauso angewandt wie die aus C++ bekannten Cast-Operatoren. Dazu muss natürlich die entsprechende Headerdatei eingebunden werden, die in diesem Fall boost/numeric/conversion/cast.hpp heißt.

boost::numeric_cast führt genau die gleiche Umwandlung von Zahlen unterschiedlicher Typen aus, wie es in C++ auch ohne Cast-Operator implizit geschieht. Der Unterschied ist jedoch, dass boost::numeric_cast überprüft, ob die Umwandlung einer Zahl so ausgeführt werden kann, dass das Ergebnis dem Originalwert entspricht und nicht verändert wird. Für das obige Programm bedeutet dies, dass keine Umwandlung durchgeführt wird. Stattdessen wird eine Ausnahme vom Typ boost::numeric::bad_numeric_cast geworfen, weil 0x10000 zu groß ist, um in einer short-Variable gespeichert werden zu können.

Genaugenommen wird keine Ausnahme vom Typ boost::numeric::bad_numeric_cast geworfen, sondern vom Typ boost::numeric::positive_overflow. Dieser Ausnahmetyp beschreibt einen sogenannten Überlauf - in diesem Fall für positive Zahlen. So gibt es auch eine Klasse boost::numeric::negative_overflow, die einen Überlauf für negative Zahlen beschreibt.

#include <boost/numeric/conversion/cast.hpp> 
#include <iostream> 

int main() 
{ 
  try 
  { 
    int i = -0x10000; 
    short s = boost::numeric_cast<short>(i); 
    std::cout << s << std::endl; 
  } 
  catch (boost::numeric::negative_overflow &e) 
  { 
    std::cerr << e.what() << std::endl; 
  } 
} 

Boost.NumericConversion definiert weitere Ausnahmetypen. Da sie alle von boost::numeric::bad_numeric_cast abgeleitet sind, können sie alle mit dieser Klasse abgefangen werden. Da boost::numeric::bad_numeric_cast seinerseits von std::bad_cast abgeleitet ist, könnte ein catch-Handler auch Ausnahmen vom Typ dieser Klasse abfangen.